//-*-Igor-*-
// ###################################################################
//  Igor Pro - JEG Tools
// 
//  FILE: "JEG NaN Marquee Points"
//
//  Author: Jonathan Guyer
//  E-mail: <jguyer@his.com>
//     WWW: <http://www.his.com/~jguyer/>
//	
//	Description: 
//		Uses marquee to set selected points in graph to NaN. This is useful for
//		excluding noise and other extraneous data from fits or plots (I take no
//		responsibility for the future of your immortal soul if you use these 
//		routines excessively). 
//	
//		To use, select a set of points in a graph with the marquee tool. Click 
//		within the marquee and select the popup menu item "JEG_NaNMarqueeData".
//		All data points from all traces within the marquee will be set to NaN,
//		after ensuring that the original trace is backed up. To customize the
//		NaNing operation, hold down the <command> key while releasing the menu
//		item (CAUTION: do not release the <command> key until the dialog 
//		appears, or the default behavior will occur); this functionality
//		requires that the GetInputState XOP (or an alias to it) be placed in
//		:Igor Extensions:.
//	
//		The functionality of this package is similar to WaveMetrics' 
//		"Delete Marquee Points", from which it borrows liberally. 
//		The primary differences are:
//			Points are set to NaN, not deleted: In the case of multiple XY 
//				pairs, deleting points can lead to serious synchronization 
//				issues.
//			Points need not be sorted: This results in somewhat slower 
//				processing than "Delete Marquee Points", but is more flexible.
//			Data may be plotted against any set of axes: "Delete Marquee Points"
//				is hard-wired to bottom-left.
//			Wave is backed up before NaN applied: The wave is stored in the 
//				package's private directory before being changed. 
//				Restore with Macros->Restore Wave from Backup. 
//				Multiple levels of undo are _not_ supported; only the original 
//				incarnation of the wave is saved.
//		
// ###################################################################

#pragma rtGlobals=1		// Use modern global access method.

#include <Keyword-Value>

#include "JEG Strings as Lists"

// -----------Interface Routines----------------------------------------

Menu "Macros"
	"Restore Wave from Backup/9", JEG_RestoreNaNedWave()
End

Macro JEG_RestoreNaNedWave(backupNum)
	Variable backupNum
	Prompt backupNum, "Wave to restore: ", popup, JEG_ListBackupWaves()

	Silent 1; PauseUpdate
	
	JEG_RestoreBackup(backupNum - 1)
End

Proc JEG_NaNifyInteraction(trace,NaNmode,checkMode,overwrite)
	String trace
	Prompt trace, "Select trace for which to NaN values: ", popup, "_all_;" + TraceNameList("",";",1)
	Variable NaNmode=NumVarOrDefault("root:Packages:'JEG NaN Values':gDeleteMarqueeModeSave", 1)
	Prompt NaNmode, "NaN", popup, "Points Inside Marquee;Points Outside Marquee"
	Variable checkMode=NumVarOrDefault("root:Packages:'JEG NaN Values':gDeleteMarqueeCheckModeSave", 3)
	Prompt checkMode, "Criteria for selecting points to NaN: ", popup, "X Values;Y Values;X and Y Values"
	Variable overwrite
	Prompt overwrite, "Overwrite backup wave(s): ", popup, "No;Yes;"
	
	PauseUpdate; Silent 1
	
	// Save input parameters for next time
	NewDataFolder/O root:Packages
	NewDataFolder/O root:Packages:'JEG NaN Values'
	Variable/G root:Packages:'JEG NaN Values':gDeleteMarqueeModeSave = NaNmode
	Variable/G root:Packages:'JEG NaN Values':gDeleteMarqueeCheckModeSave = checkMode
	
	if (cmpstr(trace,"_all_") == 0)
		JEG_NaNifyAll((NaNmode == 1),checkMode,overwrite - 1)
	else
		JEG_NaNifyTrace(trace,(NaNmode == 1),checkMode,overwrite - 1)
	endif
End

// -----------Marquee NaNing Routines----------------------------------------

Function JEG_NaNMarqueeData() : GraphMarquee
	GetMarquee
	
	if (V_flag)		// no sense in doing this if there was no marquee
		String theTrace, theInfo

		// !!! GetInputState XOP must be in :Igor Extensions:
		String state = KeyboardState("")
		
		// See if command (alt) key is pressed.
		// If so, let user select set parameters for NaNing.
		// We don't check for other modifiers because they already have other meanings:
		//		shift:		put marquee command name on command line
		//		option:		select marquee command in procedure file
		//		control:	contextual menu (MacOS 8.x only; screw Windoze)
		
		if (str2num(state[0]))
			execute "JEG_NaNifyInteraction()"
		else
			JEG_NaNifyAll(1,3,0)
		endif
	endif
	
	GetMarquee/K	// just to kill it
End

Function JEG_NaNifyAll(NaNmode,checkMode,overwrite)
	Variable NaNmode, checkMode, overwrite
	
	String theTrace, theInfo
	
	// Check each trace
	String theList = TraceNameList("",";",1)
	Variable i = 0

	do
		theTrace = GetStrFromList(theList, i, ";")
		if (strlen(theTrace) <= 0)
			break
		endif
		i += 1
		JEG_NaNifyTrace(theTrace,NaNmode,checkMode,overwrite)
	while (1)
End

// 
// -------------------------------------------------------------------------
// 
// "JEG_NaNifyTrace" --
// 
//  Check the marquee against the axes specified in theInfo.
//  If the axis specified by whichAxis and the marqee overlap, either
//  expand or contract that axis, according to the flag expand
// 
// --Version--Author------------------Changes-------------------------------
//    1.0     <jguyer@his.com> original
// -------------------------------------------------------------------------
// 
Function JEG_NaNifyTrace(trace,NaNmode,checkMode,overwrite)
	String	trace
	Variable	NaNmode, checkMode, overwrite
	
	Wave yWave = TraceNameToWaveRef("",trace)
	String theInfo = TraceInfo("",trace,0)

	String Xaxis = StrByKey("XAXIS",theInfo)
	String Yaxis = StrByKey("YAXIS",theInfo)
		
	GetAxis/Q $Xaxis
	Variable Xmin = V_min
	Variable Xmax = V_max
	
	GetAxis/Q $Yaxis
	Variable Ymin = V_min
	Variable Ymax = V_max
	
	GetMarquee $Xaxis, $Yaxis

	// if marquee exceeds axis dimensions, clip to axis.
	
	if (V_left < Xmin)
		V_left = Xmin
	endif 
	
	if (V_left > Xmax)
		V_left = Xmax
	endif
	
	if (V_right < Xmin)
		V_right = Xmin
	endif
	
	if (V_right > Xmax)
		V_right = Xmax
	endif
	
	if (V_top < Ymin)
		V_top = Ymin
	endif
	
	if (V_top > Ymax)
		V_top = Ymax
	endif
	
	if (V_bottom < Ymin)
		V_bottom = Ymin
	endif
	
	if (V_bottom > Ymax)
		V_bottom = Ymax
	endif

	// No sense checking zero-area marquees
	if ((V_bottom != V_top) %& (V_left != V_top))
		JEG_BackupWave(yWave,overwrite)
		
		Wave xWave = XWaveRefFromTrace("",trace)
		Variable i, inside, pntCount = numpnts(yWave)
		if (WaveExists(xWave))
			i = 0
			do
				inside = 1
				if (checkMode %& 1)	// checking X values
					inside = inside %& (xWave[i] >= V_left) %& (xWave[i] <= V_right)
				endif
				if (checkMode %& 2) // checking Y values
					inside = inside %& (yWave[i] >= V_bottom) %& (yWave[i] <= V_top)
				endif
				
				// NaN inside points if NaNmode is 1
				// NaN outside points if NaNmode is 0
				if (inside == NaNmode)
					yWave[i] = NaN
				endif
				
				i += 1
			while (i < pntCount)
		else
			i = 0
			Variable left = x2pnt(yWave,V_left), right = x2pnt(yWave,V_right)
			do
				inside = 1
				if (checkMode %& 1)	// checking X values
					inside = inside %& (i >= left) %& (i <= right)
				endif
				if (checkMode %& 2) // checking Y values
					inside = inside %& (yWave[i] >= V_bottom) %& (yWave[i] <= V_top)
				endif
				
				// NaN inside points if NaNmode is 1
				// NaN outside points if NaNmode is 0
				if (inside == NaNmode)
					yWave[i] = NaN
				endif
				
				i += 1
			while (i < pntCount)
		endif
	endif
End

// -----------Wave Backup Routines----------------------------------------

Function JEG_BackupWave(theWave,overwrite)
	Wave theWave
	Variable overwrite
	
	String dfSave = GetDataFolder(1)
	NewDataFolder/O/S root:Packages
	NewDataFolder/O/S 'JEG NaN Values'
	
	// !!! does backup already exist?
	String backup = JEG_FindBackup(theWave)
	if (strlen(backup) == 0)
		backup = UniqueName("backup",11,0)
		NewDataFolder/O/S $backup
		String/G path = GetWavesDataFolder(theWave,2)
		Duplicate theWave, backupWave
	else
		if (overwrite)
			SetDataFolder $backup
			String/G path = GetWavesDataFolder(theWave,2)
			Duplicate/O theWave, backupWave
		endif
	endif
	
	SetDataFolder dfSave
End

Function/S JEG_FindBackup(theWave)
	Wave theWave
	
	// !!! Assumes already located in the backup directory

	String wavePath = GetWavesDataFolder(theWave,2)
	Variable i = JEG_WhichItemInList(wavePath,JEG_ListBackupWaves(),";")
	String result = ""
	if (i >= 0)
		result = GetIndexedObjName(":",4,i)
	endif
	
	return result
End	

Function JEG_RestoreBackup(num)
	Variable num
	
	String dfSave = GetDataFolder(1)
	SetDataFolder	root:Packages:'JEG NaN Values'
	String folder = GetIndexedObjName(":",4,num)
	
	Wave w = $(":" + folder + ":backupWave")
	SVAR to = $(":" + folder + ":path") 
	Duplicate/O w, $to
	KillDataFolder $folder
	
	SetDataFolder dfSave	
End

Function/S JEG_ListBackupWaves()
	
	String dfSave = GetDataFolder(1)
	NewDataFolder/O/S root:Packages
	NewDataFolder/O/S 'JEG NaN Values'
	
	Variable i = 0
	String list = "" 
	String folder
	do
		folder = GetIndexedObjName(":",4,i)
		if (strlen(folder) == 0)
			break
		endif
		// I pity the fool who places anything in this directory besides backup waves
		SVAR backupPath = $(":" + folder + ":path")
		list += backupPath + ";"
		i += 1
	while (1)

	SetDataFolder dfSave
	
	return list
End	



